package com.zlyx.easy.netty.client.core;

import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.Map;

import com.zlyx.easy.core.hex.Hex;
import com.zlyx.easy.core.loggers.Logger;
import com.zlyx.easy.core.utils.ByteUtils;
import com.zlyx.easy.core.utils.JsonUtils;
import com.zlyx.easy.core.utils.StringUtils;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;

/**
 * 
 * @Auth 赵光
 * @Desc 描述 Netty非塞式->长连接->客户端
 * @Date 2019年4月8日
 */
public abstract class AbstractNettyClient<T> extends AbstractBaseNettyClient implements Runnable {

	protected String key;

	protected ChannelFactory factory;

	protected Class<T> tClass;

	public AbstractNettyClient(String key, String host, int port) throws Exception {
		if (key == null) {
			key = StringUtils.valueOf(this.hashCode());
		}
		this.key = key;
		this.tClass = getTClass(this.getClass());
		this.factory = new ChannelFactory(this, host, port);
	}

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		Logger.info(this.getClass(), key + "-> 客户端与服务端建立连接...");
		this.write(doOnConnection(ctx));
	}

	/**
	 * 客户端与服务端建立连接时
	 * 
	 * @param ctx
	 */
	public abstract T doOnConnection(ChannelHandlerContext ctx) throws Exception;

	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		Logger.info(this.getClass(), key + "-> 客户端与服务端连接关闭...");
		doCloseConnection(ctx);
	}

	/**
	 * 客户端与服务端断开连接时调用
	 * 
	 * @param ctx
	 */
	public void doCloseConnection(ChannelHandlerContext ctx) throws Exception {
	}

	@SuppressWarnings("unchecked")
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {
		try {
			byte[] response = (byte[]) message;
			T data = null;
			if (response != null) {
				if (Hex.class == tClass) {
					data = (T) Hex.newHex(ByteUtils.bytesToHexString(response));
				} else if (byte[].class == tClass) {
					data = (T) response;
				} else if (Map.class == tClass) {
					data = (T) JsonUtils.fromJson(new String(response), Map.class);
				} else {
					data = (T) new String(response);
				}
			}
			this.write(doService(ctx.channel(), data));
		} catch (Exception e) {
			Logger.err(e);
		} finally {
			ReferenceCountUtil.release(message);
		}
	}

	/**
	 * 长连接交互业务
	 * 
	 * @param <T>
	 * @param channnel
	 * @param msg
	 * @return
	 * @throws Exception
	 */
	public abstract T doService(Channel channel, T res) throws Exception;

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
	}

	public void write(T msg) throws Exception {
		if (msg != null) {
			if (Hex.class == tClass) {
				this.writeBytes(ByteUtils.hexStringToByte(((Hex) msg).getHex()));
			} else if (byte[].class == tClass) {
				this.writeBytes((byte[]) msg);
			} else if (Map.class == tClass) {
				this.writeBytes(msg.toString().getBytes());
			} else if (String.class == tClass) {
				this.writeBytes(((String) msg).getBytes());
			} else if (Object.class == tClass) {
				this.writeBytes(msg.toString().getBytes());
			} else {
				this.writeBytes(((String) msg).getBytes());
			}
		}
	}

	/**
	 * 向服务端输出Byte数组
	 * 
	 * @param bytes byte数组
	 * @throws Exception
	 */
	public void writeBytes(byte[] bytes) throws Exception {
		if (bytes != null) {
			Logger.info(this.getClass(), key + " -> " + Arrays.toString(bytes));
			factory.getChannel(key).writeAndFlush(bytes);
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {
		Logger.err(e);
		ctx.close();
	}

	public String getKey() {
		return key;
	}

	@Override
	public void run() {
		try {
			this.factory.getChannel(key);
			Logger.info(this.getClass(), key + "-> 建立一个连接");
		} catch (Exception e) {
			Logger.err(e);
		}
	}

	/***
	 * 关闭通道
	 */
	public void close() {
		this.factory.getChannel(key).close();
	}

	@SuppressWarnings("unchecked")
	public Class<T> getTClass(Class<?> tClass) throws Exception {
		if (tClass.getGenericSuperclass().getTypeName().contains("<")) {
			return (Class<T>) ((ParameterizedType) tClass.getGenericSuperclass()).getActualTypeArguments()[0];
		} else if (tClass != Object.class) {
			return getTClass(tClass.getSuperclass());
		}
		throw new Exception("You must define an generics to specify data types!");
	}
}
